package com.impressiveit;


import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 
 * This Utility class is for helping build hql query
 *
 * @author Junrui Tao
 * @version $Revision$
*/
public class SimpleHQLBuilder {

    enum Operator {
        lt("<"), 
        ieq("upper(", ") =", ""), 
        le("<="), 
        eq("="), 
        ge(">="), 
        gt(">"), 
        like("", " like '%'||", "||'%'"), 
        ilike("upper(", ") like '%'||", "||'%'"), 
        in(""," in (", ")"),
        or("("," or ", ")");
        
        String sqlPropertyPrefix, sqlValueSuffix, sqlOperator;

        Operator(String sqlOperator) {
            this("", sqlOperator, "");
        }

        Operator(String sqlPropertyPrefix, String sqlOperator, String sqlValueSuffix) {
            this.sqlPropertyPrefix = sqlPropertyPrefix;
            this.sqlValueSuffix = sqlValueSuffix;
            this.sqlOperator = sqlOperator;
        }
    }

    private Logger logger = LoggerFactory.getLogger(getClass());
    
    private Session session;
    protected Map<String, Object> parameter = new HashMap<String, Object>();
    private String selectFrom;
    protected StringBuilder sb;

    /***
     * SubQueryBuilder
     * To build sub query
     * null selectFrom, session
     * share parameter with parent query
     * has it's own sb StringBuilder
     *
     */
    public class SubQueryBuilder extends SimpleHQLBuilder{
    	public SubQueryBuilder(){
    		this("");
    	}
    	public SubQueryBuilder(String where) {
    		super(null,null,where);
    		parameter = SimpleHQLBuilder.this.parameter;
    	}
    }

    public SimpleHQLBuilder(Session session, String selectFrom) {
        this(session, selectFrom, "");
    }

    public SimpleHQLBuilder(Session session, String selectFrom, String where) {
        this.session = session;
        this.selectFrom = selectFrom;
        sb = new StringBuilder(where);
    }

    public SimpleHQLBuilder eq(String propertyName, Object value) {
        return addCritera(propertyName, Operator.eq, value);
    }

    public SimpleHQLBuilder ge(String propertyName, Object value) {
        return addCritera(propertyName, Operator.ge, value);
    }

    public SimpleHQLBuilder gt(String propertyName, Object value) {
        return addCritera(propertyName, Operator.gt, value);
    }

    public SimpleHQLBuilder le(String propertyName, Object value) {
        return addCritera(propertyName, Operator.le, value);
    }

    public SimpleHQLBuilder lt(String propertyName, Object value) {
        return addCritera(propertyName, Operator.lt, value);
    }

    public SimpleHQLBuilder in(String propertyName, Collection value) {
        return addCritera(propertyName, Operator.in, value);
    }

    // ignore case equal
    public SimpleHQLBuilder ieq(String propertyName, String value) {
        return addCritera(propertyName, Operator.ieq, StringUtils.upperCase(StringUtils.trimToNull(value)));
    }

    // ignore case like
    public SimpleHQLBuilder iLike(String propertyName, String value) {
        return addCritera(propertyName, Operator.ilike, StringUtils.upperCase(StringUtils.trimToNull(value)));
    }

    public SimpleHQLBuilder or(SimpleHQLBuilder[] subQueries) {
    	boolean needBracket = false;
    	StringBuilder query = new StringBuilder();
    	for (SimpleHQLBuilder b: subQueries){
    		if (!StringUtils.isBlank(b.getQueryString())){
    	        if (query.length() > 0){
    	        	query.append(" or ");
    	        	needBracket = true;
    	        }
    			query.append(b.getQueryString());
    		}
    	}
    	if (query.length()>0){
	    	appendAndToQuery();
	        sb.append(needBracket?"(":"").append(query).append(needBracket?")":"");
    	}
        return this;
    }

    // For 2 states criteria, on the page, it's a checkbox, 
    // if not checked, no to check against this criteria
    public SimpleHQLBuilder yesNo(String propertyName, boolean checked) {
        if (checked)
            return eq(propertyName, true);
        return this;
    }

    public void isNull(String propertyName) {
        appendAndToQuery();
        sb.append(propertyName).append(" is null");
    }

    private void appendAndToQuery() {
        if (sb.length() > 0)
            sb.append(" and ");
    }

    public void notNull(String propertyName) {
        appendAndToQuery();
        sb.append(propertyName).append(" is not null");
    }
    

    private SimpleHQLBuilder addCritera(String propertyName, Operator op, Object value) {
        if (value != null) {
            if (!(value instanceof String) || !StringUtils.isBlank((String) value)) {
                String key = toKey(propertyName, op);
                parameter.put(key, value);
                appendAndToQuery();
                sb.append(op.sqlPropertyPrefix).append(propertyName).append(op.sqlOperator).append(":").append(key).append(op.sqlValueSuffix);
            }
        }
        return this;
    }

    /**
     * We need to store the values to a map, so later we can set parameter.
     * This method generate a key to store the value of search criteria in the map parameter
     * Usually key is property's concat name, e.g. tc.aac.id
     * If a property name used multiple times, typical for a period search, they key will be tc.created, tc.created1, tc.created2, ... and so on 
     * @param propertyName
     * @param op
     * @return
     */
    private String toKey(String propertyName, Operator op) {
        propertyName = propertyName.replace(".", "_");
        if (parameter.get(propertyName)!=null){
            // name already exist? 
            for (int i=1;i<10;i++)
                if (parameter.get(propertyName+i)==null){
                    propertyName = propertyName +i;
                    break;
                }
            // TODO: same property used more than 10 times? Not likely 
        }
        return propertyName;
    }

    /**
     * Binding values to return Hibernate Query object 
     * @return Hibernate Query
     * 
     */
    public Query getHqlQuery() {
        return getHqlQuery("");
    }

	protected String getQueryString(){
		return sb.toString();
	}
    
    public Query getHqlQuery(String orderBy) {
        StringBuilder sql = new StringBuilder(selectFrom);
        if (sb.length()>0)
            sql.append(" where ").append(sb);
        if (!StringUtils.isBlank(orderBy))
            sql.append(" order by ").append(orderBy);
        Query q = session.createQuery(sql.toString());
        for (Map.Entry<String, Object> entry : parameter.entrySet()) {
        	if (entry.getValue() instanceof Collection)
                q.setParameterList(entry.getKey(), (Collection)entry.getValue());
        	else
        		q.setParameter(entry.getKey(), entry.getValue());
        }
        return q;
    }

}
